package com.lsh.oms.core.records;

import com.lsh.oms.core.records.core.Return;
import com.lsh.oms.core.records.core.ReturnResult;
import com.lsh.oms.core.service.tool.Criteria;
import com.lsh.oms.core.service.tool.Pageable;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

import static com.lsh.base.lang.BigDecimals.*;
import static java.math.BigDecimal.ZERO;

@Component
public class RefundRepository implements com.lsh.oms.core.records.core.RefundRepository {

    @Resource
    private ReturnRepository returns;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean apportion(Long id, Map<String, Object> request) {
        List<Return> returns = findReturns(id);
        if (returns.isEmpty()) {
            return false;
        }
        Map<Long, List<ReturnResult>> returnResults = findReturnResults(returns);
        if (!returnResultsReady(returnResults)) {
            return false;
        }
        BigDecimal total = refundAmount(returns);
        LinkedList<ReturnResultRecord.ReturnResultDetailRecord> parts = returnResults(returnResults);
        apportion(total, parts);
        refreshReturnResults(returns);
        issueReturnResults(returns);
        return true;
    }

    private List<Return> findReturns(Long id) {
        Criteria criteria = new Criteria();
        criteria.add("refund_number", id.toString());
        criteria.add("return_required", "all");
        criteria.add("details-required", "false");
        Pageable<Return> result = returns.query(criteria, 1L, 10L);
        return result.getEntries();
    }

    private Map<Long, List<ReturnResult>> findReturnResults(List<Return> returns) {
        Map<Long, List<ReturnResult>> result = new HashMap<>();
        returns.forEach($ -> {
            Criteria criteria = new Criteria();
            criteria.add("take_responsibility_required", "all");
            Pageable<ReturnResult> returnResults = $.returnResults(criteria, 1L, 10L);
            result.put($.id(), returnResults.getEntries());
        });
        return result;
    }

    private boolean returnResultsReady(Map<Long, List<ReturnResult>> returnResults) {
        return returnResults.values().stream().noneMatch(List::isEmpty);
    }

    private BigDecimal refundAmount(List<Return> returns) {
        return ((ReturnRecord) returns.get(0)).refundAmount;
    }

    private LinkedList<ReturnResultRecord.ReturnResultDetailRecord> returnResults(Map<Long, List<ReturnResult>> returnResults) {
        List<ReturnResultRecord.ReturnResultDetailRecord> results = new ArrayList<>();
        returnResults.values().forEach($ -> {
            $.forEach($$ -> {
                results.addAll(((ReturnResultRecord) $$).details.stream().map($$$ -> ((ReturnResultRecord.ReturnResultDetailRecord) $$$)).collect(Collectors.toList()));
            });
        });
        return results.stream().sorted(Comparator.comparing(ReturnResultRecord.ReturnResultDetailRecord::amount)).collect(Collectors.toCollection(LinkedList::new));
    }

    private void apportion(BigDecimal total, LinkedList<ReturnResultRecord.ReturnResultDetailRecord> parts) {
        if (zero(total)) {
            parts.forEach($ -> $.actualAmount = ZERO);
        } else {
            BigDecimal detailsTotalAmount = parts.stream().map($ -> $.amount()).reduce(ZERO, BigDecimal::add);
            parts.forEach($ -> {
                BigDecimal proportion = zero(detailsTotalAmount) ? ZERO : divideWithScale4($.amount(), detailsTotalAmount);
                $.actualAmount = multiplyWithScale2(total, proportion);
            });
        }
        if (parts.size() > 1) {
            ReturnResultRecord.ReturnResultDetailRecord last = parts.removeLast();
            last.actualAmount = subtractWithScale2(total, parts.stream().map($ -> $.actualAmount()).reduce(ZERO, BigDecimal::add));
            parts.addLast(last);
        }
        parts.forEach($ -> $.updateActualAmount());
    }

    private void refreshReturnResults(List<Return> returns) {
        Map<Long, List<ReturnResult>> freshReturnResults = findReturnResults(returns);
        freshReturnResults.values().forEach($ -> {
            $.forEach($$ -> {
                ((ReturnResultRecord) $$).actualAmount = ((ReturnResultRecord) $$).details.stream().map($$$ -> $$$.actualAmount()).reduce(ZERO, BigDecimal::add);
                $$.updateActualAmount();
            });
        });
    }

    private void issueReturnResults(List<Return> returns) {
        Map<Long, List<ReturnResult>> freshReturnResults = findReturnResults(returns);
        freshReturnResults.values().forEach($ -> {
            $.forEach($$ -> {
                $$.issue(Collections.emptyMap());
            });
        });
    }
}
